package eval

import (
	"math/big"
	"showen/num"
	"showen/obj"
	"testing"
)

func TestNumberToString(t *testing.T) {
	env := obj.NewEnvironment()

	// Test with integer
	result := NumberToString([]obj.Object{
		&obj.NumberObject{Value: num.Integer(42)},
	}, env)

	if result.Type() != obj.STRING_OBJ {
		t.Errorf("Expected string object, got %v", result.Type())
	}

	strObj := result.(*obj.StringObject)
	if strObj.Value != "42" {
		t.Errorf("Expected '42', got '%s'", strObj.Value)
	}

	// Test with negative integer
	result = NumberToString([]obj.Object{
		&obj.NumberObject{Value: num.Integer(-123)},
	}, env)

	strObj = result.(*obj.StringObject)
	if strObj.Value != "-123" {
		t.Errorf("Expected '-123', got '%s'", strObj.Value)
	}

	// Test with real number
	result = NumberToString([]obj.Object{
		&obj.NumberObject{Value: num.Real(3.14)},
	}, env)

	strObj = result.(*obj.StringObject)
	if strObj.Value != "3.14" {
		t.Errorf("Expected '3.14', got '%s'", strObj.Value)
	}

	// Test with rational number
	result = NumberToString([]obj.Object{
		&obj.NumberObject{Value: num.Rational(*big.NewRat(3, 4))},
	}, env)

	strObj = result.(*obj.StringObject)
	if strObj.Value != "3/4" {
		t.Errorf("Expected '3/4', got '%s'", strObj.Value)
	}

	// Test with complex number
	result = NumberToString([]obj.Object{
		&obj.NumberObject{Value: num.Complex{
			RealPart: num.Integer(2),
			ImagPart: num.Integer(3),
		}},
	}, env)

	strObj = result.(*obj.StringObject)
	if strObj.Value != "2+3i" {
		t.Errorf("Expected '2+3i', got '%s'", strObj.Value)
	}

	// Test error with wrong number of arguments
	result = NumberToString([]obj.Object{}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("Expected error object, got %v", result.Type())
	}

	errorObj := result.(*Error)
	if errorObj.Message != "number->string requires 1 or 2 arguments" {
		t.Errorf("Expected 'number->string requires 1 or 2 arguments', got '%s'", errorObj.Message)
	}

	// Test error with non-number argument
	result = NumberToString([]obj.Object{
		&obj.StringObject{Value: "not a number"},
	}, env)

	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("Expected error object, got %v", result.Type())
	}

	errorObj = result.(*Error)
	if errorObj.Message != "number->string first argument must be a number" {
		t.Errorf("Expected 'number->string first argument must be a number', got '%s'", errorObj.Message)
	}

	// Test with radix - integer to octal
	result = NumberToString([]obj.Object{
		&obj.NumberObject{Value: num.Integer(42)},
		&obj.NumberObject{Value: num.Integer(8)},
	}, env)

	strObj = result.(*obj.StringObject)
	if strObj.Value != "52" {
		t.Errorf("Expected '52' (octal), got '%s'", strObj.Value)
	}

	// Test with radix - integer to hexadecimal
	result = NumberToString([]obj.Object{
		&obj.NumberObject{Value: num.Integer(42)},
		&obj.NumberObject{Value: num.Integer(16)},
	}, env)

	strObj = result.(*obj.StringObject)
	if strObj.Value != "2a" {
		t.Errorf("Expected '2a' (hexadecimal), got '%s'", strObj.Value)
	}

	// Test with radix - integer to binary
	result = NumberToString([]obj.Object{
		&obj.NumberObject{Value: num.Integer(42)},
		&obj.NumberObject{Value: num.Integer(2)},
	}, env)

	strObj = result.(*obj.StringObject)
	if strObj.Value != "101010" {
		t.Errorf("Expected '101010' (binary), got '%s'", strObj.Value)
	}

	// Test error with invalid radix
	result = NumberToString([]obj.Object{
		&obj.NumberObject{Value: num.Integer(42)},
		&obj.NumberObject{Value: num.Integer(1)},
	}, env)

	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("Expected error object, got %v", result.Type())
	}

	errorObj = result.(*Error)
	if errorObj.Message != "number->string radix must be between 2 and 36" {
		t.Errorf("Expected 'number->string radix must be between 2 and 36', got '%s'", errorObj.Message)
	}

	// Test error with non-integer radix
	result = NumberToString([]obj.Object{
		&obj.NumberObject{Value: num.Integer(42)},
		&obj.NumberObject{Value: num.Real(10.0)},
	}, env)

	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("Expected error object, got %v", result.Type())
	}

	errorObj = result.(*Error)
	if errorObj.Message != "number->string second argument must be an integer (radix)" {
		t.Errorf("Expected 'number->string second argument must be an integer (radix)', got '%s'", errorObj.Message)
	}
}

func TestStringToNumber(t *testing.T) {
	env := obj.NewEnvironment()

	// Test with integer string
	result := StringToNumber([]obj.Object{
		&obj.StringObject{Value: "42"},
	}, env)

	if result.Type() != obj.NUMBER_OBJ {
		t.Errorf("Expected number object, got %v", result.Type())
	}

	numObj := result.(*obj.NumberObject)
	if numObj.Value.Type() != num.INTEGER_TYPE {
		t.Errorf("Expected integer type, got %v", numObj.Value.Type())
	}

	intObj := numObj.Value.(num.Integer)
	if int64(intObj) != int64(42) {
		t.Errorf("Expected 42, got %d", int64(intObj))
	}

	// Test with negative integer string
	result = StringToNumber([]obj.Object{
		&obj.StringObject{Value: "-123"},
	}, env)

	numObj = result.(*obj.NumberObject)
	intObj = numObj.Value.(num.Integer)
	if int64(intObj) != int64(-123) {
		t.Errorf("Expected -123, got %d", int64(intObj))
	}

	// Test with float string
	result = StringToNumber([]obj.Object{
		&obj.StringObject{Value: "3.14"},
	}, env)

	numObj = result.(*obj.NumberObject)
	if numObj.Value.Type() != num.REAL_TYPE {
		t.Errorf("Expected real type, got %v", numObj.Value.Type())
	}

	realObj := numObj.Value.(num.Real)
	if float64(realObj) != 3.14 {
		t.Errorf("Expected 3.14, got %f", float64(realObj))
	}

	// Test with integer formatted as float
	result = StringToNumber([]obj.Object{
		&obj.StringObject{Value: "5.0"},
	}, env)

	numObj = result.(*obj.NumberObject)
	// Should be converted to integer
	if numObj.Value.Type() != num.INTEGER_TYPE {
		t.Errorf("Expected integer type for 5.0, got %v", numObj.Value.Type())
	}

	intObj = numObj.Value.(num.Integer)
	if int64(intObj) != int64(5) {
		t.Errorf("Expected 5, got %d", int64(intObj))
	}

	// Test with invalid number string
	result = StringToNumber([]obj.Object{
		&obj.StringObject{Value: "not a number"},
	}, env)

	// Should return #f
	if result.Type() != obj.BOOLEAN_OBJ {
		t.Errorf("Expected boolean object, got %v", result.Type())
	}

	boolObj := result.(*obj.BooleanObject)
	if boolObj.Value != false {
		t.Errorf("Expected false, got %t", boolObj.Value)
	}

	// Test error with wrong number of arguments
	result = StringToNumber([]obj.Object{}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("Expected error object, got %v", result.Type())
	}

	errorObj := result.(*Error)
	if errorObj.Message != "string->number requires 1 or 2 arguments" {
		t.Errorf("Expected 'string->number requires 1 or 2 arguments', got '%s'", errorObj.Message)
	}

	// Test error with non-string argument
	result = StringToNumber([]obj.Object{
		&obj.NumberObject{Value: num.Integer(42)},
	}, env)

	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("Expected error object, got %v", result.Type())
	}

	errorObj = result.(*Error)
	if errorObj.Message != "string->number first argument must be a string" {
		t.Errorf("Expected 'string->number first argument must be a string', got '%s'", errorObj.Message)
	}

	// Test with radix - octal string
	result = StringToNumber([]obj.Object{
		&obj.StringObject{Value: "52"},
		&obj.NumberObject{Value: num.Integer(8)},
	}, env)

	numObj = result.(*obj.NumberObject)
	intObj = numObj.Value.(num.Integer)
	if int64(intObj) != int64(42) {
		t.Errorf("Expected 42, got %d", int64(intObj))
	}

	// Test with radix - hexadecimal string
	result = StringToNumber([]obj.Object{
		&obj.StringObject{Value: "2a"},
		&obj.NumberObject{Value: num.Integer(16)},
	}, env)

	numObj = result.(*obj.NumberObject)
	intObj = numObj.Value.(num.Integer)
	if int64(intObj) != int64(42) {
		t.Errorf("Expected 42, got %d", int64(intObj))
	}

	// Test with radix - binary string
	result = StringToNumber([]obj.Object{
		&obj.StringObject{Value: "101010"},
		&obj.NumberObject{Value: num.Integer(2)},
	}, env)

	numObj = result.(*obj.NumberObject)
	intObj = numObj.Value.(num.Integer)
	if int64(intObj) != int64(42) {
		t.Errorf("Expected 42, got %d", int64(intObj))
	}

	// Test error with invalid radix
	result = StringToNumber([]obj.Object{
		&obj.StringObject{Value: "42"},
		&obj.NumberObject{Value: num.Integer(1)},
	}, env)

	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("Expected error object, got %v", result.Type())
	}

	errorObj = result.(*Error)
	if errorObj.Message != "string->number radix must be between 2 and 36" {
		t.Errorf("Expected 'string->number radix must be between 2 and 36', got '%s'", errorObj.Message)
	}

	// Test error with non-integer radix
	result = StringToNumber([]obj.Object{
		&obj.StringObject{Value: "42"},
		&obj.NumberObject{Value: num.Real{Value: 10.0}},
	}, env)

	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("Expected error object, got %v", result.Type())
	}

	errorObj = result.(*Error)
	if errorObj.Message != "string->number second argument must be an integer (radix)" {
		t.Errorf("Expected 'string->number second argument must be an integer (radix)', got '%s'", errorObj.Message)
	}
}
